MFC滚动条(CScrollBar)控件 自绘 | 您所在的位置:网站首页 › mfc 单选框自绘 按下第几个 › MFC滚动条(CScrollBar)控件 自绘 |
自绘是在滚动条WM_PAINT消息处理函数里完成的。第一步是得知道,滚动条的各组件大小信息,如左按钮宽度,滑块位置大小,右通道大小等,这些信息的获取可以用API函数GetScrollBarInfo来完成 那么我就在CScrollBar的派生类CNewScrollBar定义了6个变量CRect,对应着上面的信息。 CRect m_cliRect;//滚动条大小 CRect m_rcLeftButton;//左箭头按钮大小 CRect m_rcRightButton;//右箭头按钮大小 CRect m_rcThumb;//滑块位置,大小 CRect m_rcLeftChannel;//左通道大小 CRect m_rcRightChannel;//左通道大小 好接下来说说,怎么给它们正确赋值吧,GetScrollBarInfo得到的只是一些简单数据,如按钮宽度,滑块位置,宽度。那些通道还是得我们自己来计算。 GetScrollBarInfo函数定义: BOOL GetScrollBarInfo( HWND hwnd,/./滚动条窗口句柄,或者窗口拥有滚动条属性的窗口句柄(这是假滚动条,跟窗口一体的) LONG idObject,//如果是滚动条窗口句柄,填OBJID_CLIENT,如果是“假滚动条”,水平填OBJID_HSCROLL ,垂直OBJID_VSCROLL PSCROLLBARINFO psbi);//这个结构包含的就是滚动条的各项信息了。 (如果编译代码的时候,出现SCROLLBARINFO结构未定义,在Stdafx.h头文件里最前面加上#define WINVER 0x500)
SCROLLBARINFO 结构定义: typedef struct tagSCROLLBARINFO { DWORD cbSize;//初始化结构体,须赋值sizeof(SCROLLBARINFO),也就是结构体大小 RECT rcScrollBar;//滚动条大小,位置,这个是相对于屏幕,不受父窗口限制。也就是调用GetWindowRect函数获取的大小。 int dxyLineButton;//按钮宽度(水平),或按钮高度(垂直) int xyThumbTop;//滑块左边位置(水平),滑块顶部位置(垂直),这个位置是相对于滚动条的。 int xyThumbBottom;//滑块右边位置(水平),滑块底部位置(垂直)。相对于滚动条 int reserved;//预留。。。 DWORD rgstate[CCHILDREN_SCROLLBAR+1];//指明各组件状态,如按钮被按下,详细查MSDN,这个例子不会用到它。 } SCROLLBARINFO, *PSCROLLBARINFO, *LPSCROLLBARINFO; 为省事,我在WM_PAINT消息处理函数计算出各项信息。如下: //获取滚动条信息,按钮大小,滑块大小等 SCROLLBARINFO sbi; sbi.cbSize=sizeof(SCROLLBARINFO); ::GetScrollBarInfo(this->m_hWnd,OBJID_CLIENT,&sbi); //滚动条大小 m_cliRect=sbi.rcScrollBar; //计算左箭头按钮大小 m_rcLeftButton=CRect(0,0,sbi.dxyLineButton,m_cliRect.Height()); //计算右箭头按钮大小 m_rcRightButton=CRect(m_cliRect.Width()-sbi.dxyLineButton,0,m_cliRect.Width(),m_cliRect.Height()); //计算滑块位置 m_rcThumb=CRect(sbi.xyThumbTop,0,sbi.xyThumbBottom,m_cliRect.Height()); //计算左通道大小 m_rcLeftChannel=CRect(sbi.dxyLineButton,0,m_rcThumb.left,m_cliRect.Height()); //计算右通道大小 m_rcRightChannel=CRect(m_rcThumb.right,0,m_cliRect.Width()-sbi.dxyLineButton,m_cliRect.Height());
其实就算你在WM_PAINT消息处理中什么都不做,也不调用父类WM_PAINT消息处理函数,父类也是有机会绘制滚动条的,而我们要完全自绘,不需要父类来处理。这样总会出错。就比如双击滚动条,绘制滚动条,不通过WM_PAINT消息处理函数。。。所以我们就要禁止掉父类处理WM_LBUTTONDBLCLK消息,方法也很简单,给CNewScrollBar添加WM_LBUTTONDBLCLK消息处理函数,在那函数里不调用父类双击消息处理函数就行了。当然这只是举个例子。后面还是会有父类绘制滚动条的问题。。。比如,调用SetScrollPos函数设置滚动条位置的时候,再一次逃脱自绘的范围了。父类又参与绘制滚动条了。。。就是由于调用SetScrollPos设置滚动条位置,会给滚动条发送SBM_SETSCROLLINFO消息,其实也就是通过发送消息的方式来设置滚动信息。那么在父类这个消息处理函数里,肯定也绘制了滚动条。。所以我们要做的,就截获掉SBM_SETSCROLLINFO,不让父类处理。。。可是这里有一个问题,如果不让父类处理,那么数据也是对接不上了。。。毕竟存储滚动条诸如位置的信息,都是得父类来处理。我看过一个例子,就是完全用自己的数据来代替,也就是滚动条页大小,位置,也由新类来存储,设置。后面会解决这个问题的,现在先放一旁,来看看消息发送。 滚动条发送消息给父窗口的代码示例,如拖动滚动条,左右按钮被单击,如下: GetParent()->SendMessage(WM_HSCROLL,MAKELONG(SB_LINELEFT,0),(LPARAM)this->m_hWnd);//左(上)按钮被单击 GetParent()->SendMessage(WM_HSCROLL,MAKELONG(SB_LINERIGHT,0),(LPARAM)this->m_hWnd);//右(下)按钮被单击 GetParent()->SendMessage(WM_HSCROLL,MAKELONG(SB_PAGELEFT,0),(LPARAM)this->m_hWnd);//左能道单击了下 GetParent()->SendMessage(WM_HSCROLL,MAKELONG(SB_PAGERIGHT,0),(LPARAM)this->m_hWnd);//右通道单击了下 GetParent()->SendMessage(WM_HSCROLL,MAKELONG(SB_THUMBTRACK,nPos),(LPARAM)this->m_hWnd);//拖动滑块 用到也只上面5个消息,另外的消息不需要也可以,这里也特别说一下SB_THUMBTRACE这个消息,这个消息附带的参数,要指明滑块位置。也就是那个nPos,nPos是给调用者设置滑块位置用的。而这个Pos需要我们自己来求出(拖动滑块时),这里我就给一个简单的计算方法, 假设设置最大滑块位置是255,页大小是50.但实际滑块位置在205时,就到头了。原因是设置了页大小50。所以滑块位置的可滚动范围是0~205(减去页大小50)。那么滑块位置范围是0~205,而滚动的像素范围是0~235。那们我们就可以求出1个像素滚动多少点(比例)也就是205/235。具体应用代码就是:
在派生出的滚动条类定义一个double变量,存储比例,一个CRect变量,存储滚动动大小. double m_Ratio;//比例 CRect m_cliRect;//滚动条大小 在WM_PAINT消息处理函数中,计算比例(接上面计算滚动各项信息的代码) //获取滚动条信息,页大小,滑块位置 SCROLLINFO si; si.cbSize=sizeof(SCROLLINFO); GetScrollInfo(&si); //计算通道可滚动像素范围 int chlWidth=m_cliRect.Width()-sbi.dxyLineButton*2-m_rcThumb.Width(); //计算滑块可移动的最大位置 int maxPos=si.nMax-si.nPage; //计算比率,1个像素多少点 m_Ratio=(double)maxPos/chlWidth; 求出了比例,接着来计算鼠标拖动滑块移动时,滚动条正确位置,这里还要在派生类里定义一些变量,如下: BOOL m_isTrace;//指明鼠标当前是否在滑块内,并且鼠标左键是按钮下状态 int m_nSize;//鼠标单击在滑块上,距滑块左边的距离 上面的m_nSize,在WM_LBUTTONDOWN消息处理函数求出,以上面的图为例,鼠标左键在X轴248像素处按下了,那么m_nSize的值 就是248-235,具体看WM_LBUTTONDOWN消息处理函数中的代码: void CNewScrollBar::OnLButtonDown(UINT nFlags, CPoint point) { if(m_rcThumb.PtInRect(point)) { m_isTrace=TRUE; m_nSize=point.x-m_rcThumb.left; SetCapture(); } //CScrollBar::OnLButtonDown(nFlags, point); 不让父类处理 } 那么在WM_MOVEMOUSE消息处理函数中,代码就是这样的: void CNewScrollBar::OnMouseMove(UINT nFlags, CPoint point) { // TODO: Add your message handler code here and/or call default if(m_isTrace) { //-17是左边的按钮宽度,用常量代替了,动态求按钮宽度的话,在类中定义一个变量就可以了,这里就不这样做了。 int nPos=(point.x-m_nSize-17)*m_Ratio; GetParent()->SendMessage(WM_HSCROLL,MAKELONG(SB_THUMBTRACK,nPos),(LPARAM)this->m_hWnd); } //CScrollBar::OnMouseMove(nFlags, point); } 好了,总算完成了最困难的SB_THUMBTRACK消息发送,剩下几个消息的正确发送,参照工程里的代码吧,这里就不提了。前面说过要拦截SBM_SETSCROLLINFO消息,防止父类参与绘制滚动条,这里就简单的处理下,这样做当然有很多弊端,但这是学习用的,从简到难。现在也不需要做出什么完善,漂亮的滚动来,有了这个基础,等以后需要的时候再来做。算是入门吧。 给CNewScrollBar添加虚函数WindowProc,该函数的代码如下: LRESULT CNewScrollBar::WindowProc(UINT message, WPARAM wParam, LPARAM lParam) { // TODO: Add your specialized code here and/or call the base class if(message==SBM_SETSCROLLINFO) { LRESULT res= CScrollBar::WindowProc(message, wParam, lParam);//让父类处理 //CNewScrollBar没有自己的滚动条信息结构,所以还得依靠父类。调用上面的函数,会让父类绘制滚动条。这是我们不想见到的 //所以在下面立即刷新,这里增加了一次不必要的绘制,如果要避免的话,必须拥有自己的信息结构也就是SCROLLINFO来自己设置 //后面我会给一个工程,这是个完整的工程,这个工程是我在CodeProject网站里下载的,里面避免了调用父类WindowProc方法 this->Invalidate();//立即刷新 return res; } return CScrollBar::WindowProc(message, wParam, lParam); }
先单击“Button1"设置滚动条信息。我只是简单的画了下,有兴趣的朋友可以用位图来绘制一下。另外,如果快速单击按钮的话,会发现有一定的间隔,速度跟不上标准的滚动条,这是因为滚动条的窗口类支持鼠标双击(CS_DBLCLKS),我不知道标准的滚动条是怎么做到的,既支持双击,速度又够快。而我只能去掉窗口类的双击属性。反正我也用不了双击,这也并没有什么影响。 方法是给CNewScrollBar添加虚函数PreCreateWindow,在创建滚动条的时候,改变它的窗口类。如下: BOOL CNewScrollBar::PreCreateWindow(CREATESTRUCT& cs) { // TODO: Add your specialized code here and/or call the base class WNDCLASS wndcls; //窗口类名ScrollBar,也就是wndcls.lpszClassName,已经注册了,创建的时候直接用 //下面这个函数获取窗口类信息,以wndcls.lpszClassName做标识 GetClassInfo(NULL,"ScrollBar",&wndcls); wndcls.style^=CS_DBLCLKS;//去掉双击属性 wndcls.lpszClassName="newScrollBar";//新窗口类名 cs.lpszClass=wndcls.lpszClassName; AfxRegisterClass(&wndcls);//注册窗口类 return CScrollBar::PreCreateWindow(cs); } 但是对上面的那个滚动条并没有效,依然产生双击消息,原因是上面滚动条是对话框里的资源,创建的时候,并没有通过PreCreateWindow函数,而是系统直接创建了,然后我估计调用类似SubClassWindow的函数再给其关联起来。所以这个方法只对调用控件里的Create函数创造的滚动条有效。Create函数创建滚动条的代码示例如下: CNewScrollBar m_sr; CRect rect=CRect(0,0,200,17); m_sr.Create(WS_CHILD|WS_VISIBLE,rect,this,1002); 一试,单击响应速度果然没有间隔了。 记得先单击“Button1”按钮设置滚动信息。 另外我从CodeProject下载的工程地址:http://www.codeproject.com/KB/dialog/skinscrollbar.aspx 这个工程比较完善,可以参考一下。我就是参考它的。。。。。。 如果实现滚动条热点功能,可以用定时器,来实现,每隔一段时间,判断。(鼠标离开滚动条时取消计时,节约资源) |
CopyRight 2018-2019 实验室设备网 版权所有 |